#ifndef BOSS_Afterburner_MappedObject_H
#define BOSS_Afterburner_MappedObject_H

#include <string>
#include <unordered_map>

/// @addtogroup Afterburner_objects
/// @{

/// Derive from this base object using the [Curiously Recurring Template Pattern (CRTP)](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) and all instances of the derived object will be keep tracked of in a hash table (`unordered_map`) unique to that class.
/// @author   Remco de Boer 雷穆克 (r.e.deboer@students.uu.nl or remco.de.boer@ihep.ac.cn)
/// @date     April 13th, 2019
template <class T>
class MappedObject
{
public:
  MappedObject(const std::string& identifier) { SetIdentifier(identifier); }
  ~MappedObject() { RemoveInstance(); }

  void               SetIdentifier(const std::string& identifier);
  const std::string& GetIdentifier() const { return fIdentifier; }

  static T*           GetInstance(const std::string& identifier);
  static const Ssiz_t GetNInstances() { return fCount; }
  static const bool   HasInstance(const std::string& identifier);

  static void              IterReset() { fIterator = fInstances.begin(); }
  static const bool        IterIsEnd() { return fIterator == fInstances.end(); };
  static T*                IterNext();
  static T*                IterGetObject();
  static const std::string IterGetID();

private:
  std::string   fIdentifier;
  static size_t fCount;

  static typename std::unordered_map<std::string, const MappedObject<T>*>           fInstances;
  static typename std::unordered_map<std::string, const MappedObject<T>*>::iterator fIterator;

  MappedObject(const MappedObject& copyFrom) {}
  void InsertInstance();
  void RemoveInstance();
};

/// @}

template <class T>
typename std::unordered_map<std::string, const MappedObject<T>*> MappedObject<T>::fInstances;
template <class T>
typename std::unordered_map<std::string, const MappedObject<T>*>::iterator
  MappedObject<T>::fIterator{fInstances.end()};
template <class T>
size_t MappedObject<T>::fCount{0};

template <class T>
void MappedObject<T>::SetIdentifier(const std::string& identifier)
{
  RemoveInstance();
  fIdentifier = identifier;
  InsertInstance();
}

template <class T>
void MappedObject<T>::RemoveInstance()
{
  if(HasInstance(fIdentifier))
  {
    --fCount;
    fInstances.erase(fIdentifier);
  }
}

template <class T>
void MappedObject<T>::InsertInstance()
{
  if(HasInstance(fIdentifier))
    throw BOSS_Afterburner::Error::Exception(
      Form("Object \"%s\" already exists", fIdentifier.c_str()));
  ++fCount;
  fInstances.emplace(fIdentifier, this);
}

template <class T>
T* MappedObject<T>::GetInstance(const std::string& identifier)
{
  if(HasInstance(identifier))
    return (T*)fInstances.at(identifier);
  else
    return nullptr;
}

template <class T>
const bool MappedObject<T>::HasInstance(const std::string& identifier)
{
  return fInstances.find(identifier) != fInstances.end();
}

template <class T>
T* MappedObject<T>::IterNext()
{
  if(IterIsEnd()) return nullptr;
  ++fIterator;
  return IterGetObject();
}

template <class T>
T* MappedObject<T>::IterGetObject()
{
  if(IterIsEnd()) return nullptr;
  return (T*)fIterator->second;
}

template <class T>
const std::string MappedObject<T>::IterGetID()
{
  if(IterIsEnd()) return "";
  return fIterator->first;
}

#endif